--object class extended to support virtual properties and active properties.
--virtual properties call class:get_<name>() and class:set_<name>(value)
--to get/set the indexed value. active properties are properties with a
--setter but no getter: the value is stored in object.__state and
--class:set_<name>(new_value, old_value) is called if the new value is
--different than the one already stored. object.__state must be initialized
--in the object constructor with appropriate values since there's no getter.
setfenv(1, require'winapi.oo')
require'winapi.oo.object'

VObject = class(Object)

function VObject:__subclass(c) --class constructor
	VObject.__index.__subclass(self,c)
	c.__meta.__index = function(o,k)
		if c[k] ~= nil then return c[k] end --method
		return c:__get_property(o,k) --virtual or property
	end
	c.__meta.__newindex = function(o,k,v)
		c:__set_property(o,k,v) --raw, virtual or active property
	end
end

function VObject:__get_property(o,k)
	if self['get_'..tostring(k)] then --virtual property
		return self['get_'..tostring(k)](o)
	elseif rawget(o, '__state') and o.__state[k] then --active property
		return o.__state[k]
	end
end

function VObject:__set_property(o,k,v)
	if not self['get_'..tostring(k)] then
		if not self['set_'..tostring(k)] then --raw property
			rawset(o,k,v)
		else --active property
			if not o.__state then rawset(o, '__state', {}) end
			if v ~= o.__state[k] then
				self['set_'..k](o,v,o.__state[k])
				o.__state[k] = v
			end
		end
	else --virtual property
		self['set_'..k](o,v)
	end
end


if not ... then
local c = class(VObject)
function c:__init(...) print('init:', ...) end
function c:get_status() print'virtual property access: "status": OK'; return 'OK'; end
function c:set_status(s) print('virtual property set: "status": %s' % s) end
function c:set_caption(s) print('active property set "caption": %s' % s) end
local o = c('hi', 'there')
assert(o.__index == VObject) --parent class
assert(o.status == 'OK') --virtual property get
o.status = 'EVEN BETTER' --virtual property set
assert(o.__state == nil) --no active properties yet
o.caption = 'hello' --active property set
assert(o.caption == 'hello') --active property get
assert(o.__state.caption == o.caption) --confirm the active property
assert(o.unknown == nil) --non-existent property
assert(o[1234] == nil) --non-existent property
assert(o[false] == nil) --non-existent property
end

